package com.yfqy.app.security.resourceapi;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.yfqy.app.enums.ErrorCode;
import com.yfqy.app.exception.BusinessException;
import com.yfqy.app.security.login.LoginUserInfo;
import com.yfqy.app.service.JwtService;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * JWT认证过滤器 - 负责验证JWT token并设置安全上下文
 * OncePerRequestFilter确保每个请求只执行一次过滤
 * <p>
 * 加上 @Component注解 会被 Spring Boot 自动注册为全局过滤器，过滤器链顺序不影响全局过滤器
 * 即使设置了 @Order 的过滤器链顺序，全局过滤器仍会独立执行
 */
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private static final String BEARER_PREFIX = "Bearer ";
    private static final int BEARER_PREFIX_LENGTH = BEARER_PREFIX.length();

    private final JwtService jwtService;

    public JwtAuthenticationFilter(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    @NonNull HttpServletResponse response,
                                    @NonNull FilterChain filterChain) throws ServletException, IOException {
        String requestUri = request.getRequestURI();

        log.debug("开始处理JWT认证，请求URI: {}", requestUri);
        if (isPublicRequest(requestUri)) {
            filterChain.doFilter(request, response);
            return;
        }
        try {
            String jwtToken = extractJwtToken(request);
            if (StringUtils.isEmpty(jwtToken)) {
                log.warn("请求缺少JWT token: {}", requestUri);
                throw new BusinessException(ErrorCode.USER_NOT_LOGIN.getCode(), "token缺失");
            }

            LoginUserInfo loginUserInfo = jwtService.verifyJwt(jwtToken, LoginUserInfo.class);
            log.debug("JWT token验证成功，用户ID: {}, 登录类型: {}", loginUserInfo.getId(), loginUserInfo.getLoginType());

            setupSecurityContext(loginUserInfo, jwtToken);

            log.debug("安全上下文设置完成，继续过滤器链");
            filterChain.doFilter(request, response);

        } catch (ExpiredJwtException e) {
            log.warn("JWT token已过期: {}", e.getMessage());
            throw new BusinessException(HttpStatus.UNAUTHORIZED.value(), "token过期");
        } catch (BusinessException e) {
            log.warn("业务异常: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("JWT token解析失败: {}", e.getMessage(), e);
            throw new BusinessException(HttpStatus.UNAUTHORIZED.value(), "token解析失败");
        }
    }

    /**
     * 从请求头中提取JWT token
     */
    private String extractJwtToken(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (StringUtils.isNotEmpty(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
            return authHeader.substring(BEARER_PREFIX_LENGTH);
        }
        return authHeader;
    }

    /**
     * 设置安全上下文
     */
    private void setupSecurityContext(LoginUserInfo loginUserInfo, String jwtToken) {
        log.debug("设置用户上下文: userId={}, username={}",
                loginUserInfo.getId(), loginUserInfo.getNickname());

        JwtAuthentication authentication = new JwtAuthentication(loginUserInfo);
        authentication.setJwtToken(jwtToken);
        authentication.setAuthenticated(true);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    /**
     * 可选：排除不需要JWT验证的路径
     */
    private boolean isPublicRequest(String path) {
        // 可以在这里添加不需要过滤的路径，如登录、公开API等
        boolean isPublicReq = path.contains("/noauth/") || path.contains("/public/");
        if (isPublicReq) {
            LoginUserInfo loginUserInfo = new LoginUserInfo();
            JwtAuthentication authentication = new JwtAuthentication(loginUserInfo);
            authentication.setJwtToken("");
            authentication.setAuthenticated(true);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        return isPublicReq;
    }
}